﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.ServiceModel;

namespace GUIL
{
    // Callback is not allowed to use current sunchranizaion context
    [CallbackBehavior(
        ConcurrencyMode = ConcurrencyMode.Single,
        UseSynchronizationContext = false)]
    public partial class SeatGUI : Form, RemoteService.IRemoteServiceCallback
    {
        SynchronizationContext _uiSyncContext;
        RemoteService.IRemoteService _remote;
        RemoteService.MovieHall _movieHall;
        RemoteService.ScreenCast _screenCast;
        List<RemoteService.ScreenCast_Seat> _selectedSeats;
        List<RemoteService.ScreenCast_Seat> _screenSeats;
        int _NumOfNormalSeats;
        int _NumOfChildrenSeats;
        int _NumOfSeniorSeats;
        System.Windows.Forms.Timer _timer;

        public List<RemoteService.PartBooking> partBookings { get; private set; } // make accessible for parent object
        
        public SeatGUI(RemoteService.MovieHall movieHall, 
            RemoteService.ScreenCast screenCast)
        {
            try
            {
                // Hosting client interface ... (more info later)
                // Connection hasn't been opened yet!
                _remote = new RemoteService.RemoteServiceClient(new InstanceContext(this), "WSDualHttpBinding_IRemoteService");

                // Add client to subscriber list
                _remote.Subscribe(screenCast);

                _screenSeats = _remote.GetScreenCastSeats(screenCast);

                // Make sure that seats are in right order
                _screenSeats = _screenSeats.OrderBy(sc => sc.Seat.Row).ThenBy(sc => sc.Seat.Number).ToList();
            }
            catch (Exception)
            {
                MessageBox.Show("No connection!");
            }
            

            // Get synchronization context for UI
            _uiSyncContext = SynchronizationContext.Current;

            _movieHall = movieHall;
            _screenCast = screenCast;

            _selectedSeats = new List<RemoteService.ScreenCast_Seat>();
            partBookings = new List<RemoteService.PartBooking>();
            _NumOfNormalSeats = 0;
            _NumOfChildrenSeats = 0;
            _NumOfSeniorSeats = 0;
            _timer = new System.Windows.Forms.Timer();

            InitializeComponent();
        }

        private void SeatGUI_Load(object sender, EventArgs e)
        {
            SetupDataGridViewSeats();
            ColorSeatsByStatus();

            // initiate drop downs to zero, actual numbers are dynamicly created
            comboBoxNormal.Items.Add(0);
            comboBoxChildren.Items.Add(0);
            comboBoxSenior.Items.Add(0);
            comboBoxNormal.SelectedItem = 0;
            comboBoxChildren.SelectedItem = 0;
            comboBoxSenior.SelectedItem = 0;

            // seat counts set to zero by default
            labelSelectedSeats.Text = "0";

            // set book button as disabled (cant book 0 seats)
            buttonBookSeats.Enabled = false;

            // set find seats button as disabled (cant find zero seats)
            buttonFindSeats.Enabled = false;

            // create timer and start it
            _timer.Interval = 120000; // 2 min
            _timer.Tick += _timer_Tick;
            _timer.Enabled = true;
            _timer.Start();
        }

        void _timer_Tick(object sender, EventArgs e)
        {
            FreeSeats();
            MessageBox.Show("Seats have been set to free because of inactivity");
        }

        /// <summary>
        /// Setup data grid view from seats
        /// </summary>
        private void SetupDataGridViewSeats()
        {
            // get number of columns and rows in movie hall
            dataGridViewSeats.ColumnCount = _screenSeats.Max(sc => sc.Seat.Number);
            dataGridViewSeats.RowCount = _screenSeats.Max(sc => sc.Seat.Row);

            // clear selection from start
            dataGridViewSeats.ClearSelection();
        }

        /// <summary>
        /// Color seats in hall and change status according to selected seats
        /// </summary>
        private void ColorSeatsByStatus()
        {
            // color all seats in hall
            foreach (var item in _screenSeats)
            {
                // set correct seatavailable status on screen seat, based on selected seat
                RemoteService.ScreenCast_Seat _tmpSeat = _selectedSeats.Where(ss => ss.Id == item.Id).SingleOrDefault();
                if (_tmpSeat != null) // only if seat exist in selected seats
                {
                    item.SeatAvailable = _tmpSeat.SeatAvailable;
                }

                // color seats accordingly
                if (_selectedSeats.Any(ss => ss.Id == item.Id)) // selected
                {
                    dataGridViewSeats.Rows[item.Seat.Row - 1].Cells[item.Seat.Number - 1].Style.BackColor = Color.Gold;
                }
                else if (item.SeatAvailable) // free
                {
                    dataGridViewSeats.Rows[item.Seat.Row - 1].Cells[item.Seat.Number - 1].Style.BackColor = Color.LightGreen;
                }
                else // taken
                {
                    dataGridViewSeats.Rows[item.Seat.Row - 1].Cells[item.Seat.Number - 1].Style.BackColor = Color.DarkRed;
                }
            }
        }

        /// <summary>
        /// Activate bestSeat algorithm
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonFindSeats_Click(object sender, EventArgs e)
        {
            // Free all seats, so that the seats that the algorithm chooses are the only chosen ones.
            FreeSeats();

            try
            {
                // Call algorithm and get best seats in movie hall.
                _selectedSeats = _remote.GetBestSeats(_screenSeats, (_NumOfNormalSeats + _NumOfChildrenSeats + _NumOfSeniorSeats), null);

                // Seat all selected seat to false
                _selectedSeats.ForEach(s => s.SeatAvailable = false);

                // Update db with seat status
                _remote.ChangeSeatsAvailability(_selectedSeats);
            }
            catch (Exception)
            {
                MessageBox.Show("Seems like someone already booked the seats, please try some other seats.");
            }

            // if selected seats > 0 and equal to number of seat in dropdowns then enable 'book seats' button, otherwise disable
            buttonBookSeats.Enabled = _selectedSeats.Count > 0 && (_NumOfChildrenSeats + _NumOfNormalSeats + _NumOfSeniorSeats) == _selectedSeats.Count;

            // set selected seats number
            labelSelectedSeats.Text = _selectedSeats.Count.ToString();

            ColorSeatsByStatus();

            // restart timer
            _timer.Stop();
            _timer.Start();
        }

        /// <summary>
        /// Free selected seats in gui and DB
        /// </summary>
        private void FreeSeats() 
        {
            // Set seats to true
            _selectedSeats.ForEach(s => s.SeatAvailable = true);

            if (_selectedSeats.Count > 0)
            {
                try
                {
                    // Change seatabilablity in db.
                    _remote.ChangeSeatsAvailability(_selectedSeats);
                }
                catch (Exception)
                {
                    MessageBox.Show("Seat status has changed in db, try again.");
                }

                ColorSeatsByStatus();
            }

            _selectedSeats.Clear();
            labelSelectedSeats.Text = _selectedSeats.Count.ToString();

        }

        private void dataGridViewSeats_CellMouseClick(object sender, MouseEventArgs e)
        {
            // get current selected cell
            DataGridViewCell selectedCell = dataGridViewSeats.CurrentCell;

            // ignore if user presses enything else than free or selected seat
            // can only select seats when selected seats are less than seats from dropdown
            if ((selectedCell.Style.BackColor == Color.LightGreen && (_NumOfChildrenSeats + _NumOfNormalSeats + _NumOfSeniorSeats) > _selectedSeats.Count) || selectedCell.Style.BackColor == Color.Gold)
            {
                // get selected seat
                RemoteService.ScreenCast_Seat _tmpScreenSeat = _screenSeats.Where(s => s.Seat.Number == selectedCell.ColumnIndex + 1 && s.Seat.Row == selectedCell.RowIndex + 1).Single();

                // set seat state
                _tmpScreenSeat.SeatAvailable = (selectedCell.Style.BackColor != Color.LightGreen);

                // add/remove seat to/from selected seats
                if (selectedCell.Style.BackColor == Color.LightGreen)
                {
                    _selectedSeats.Add(_tmpScreenSeat);
                }
                else
                {
                    _selectedSeats.RemoveAll(s => s.SeatId == _tmpScreenSeat.SeatId);
                }

                // save new seat status to db
                _remote.ChangeSeatsAvailability(new List<RemoteService.ScreenCast_Seat> { _tmpScreenSeat });
                
                // color selected seat
                ColorSeatsByStatus();

                // set selected seats number
                labelSelectedSeats.Text = _selectedSeats.Count.ToString();

                // if selected seats > 0 and equal to number of seat in dropdowns then enable 'book seats' button, otherwise disable
                buttonBookSeats.Enabled = _selectedSeats.Count > 0 && (_NumOfChildrenSeats + _NumOfNormalSeats + _NumOfSeniorSeats) == _selectedSeats.Count;
            }

            // restart timer
            _timer.Stop();
            _timer.Start();
        }

        private void buttonBookSeats_Click(object sender, EventArgs e)
        {
            // create dictionary with age group values
            Dictionary<int, int> ageGrops = new Dictionary<int, int>();
            for (int i = 1; i <= _NumOfNormalSeats + _NumOfChildrenSeats + _NumOfSeniorSeats; i++)
            {
                if (i <= _NumOfNormalSeats) // if normal seat use 1
                {
                    ageGrops.Add(i, 1);
                }
                else if (i <= _NumOfNormalSeats + _NumOfChildrenSeats) // if children use 2
                {
                    ageGrops.Add(i, 2);
                }
                else // if senior use 3
                {
                    ageGrops.Add(i, 3);
                }
            }

            int ageGroupIndex = 1; // set to first element in ageGroups, auto increments in call
            foreach (var item in _selectedSeats) // add new partbookings based on selected seats
            {
                partBookings.Add(new RemoteService.PartBooking 
                                    {
                                        AgeGroup = ageGrops[ageGroupIndex++].ToString(),
                                        ScreenCast_Seat = item
                                    });
            }

            this.DialogResult = DialogResult.OK;
            this.Close();
        }

        /// <summary>
        /// Set number of available seats on selected drop down when entered
        /// </summary>
        private void SeatsTypeDropDowns_Enter(object sender, EventArgs e)
        {
            ComboBox selectedBox = ((ComboBox)sender);
            // get number of available seats
            int numOfAvailableSeats = _screenSeats.Where(ss => ss.SeatAvailable == true).Count() + _selectedSeats.Count;
            // minus already required seats
            switch (selectedBox.Name)
            {
                case "comboBoxNormal":
                    numOfAvailableSeats -= _NumOfChildrenSeats + _NumOfSeniorSeats;
                    break;
                case "comboBoxChildren":
                    numOfAvailableSeats -= _NumOfNormalSeats + _NumOfSeniorSeats;
                    break;
                case "comboBoxSenior":
                    numOfAvailableSeats -= _NumOfNormalSeats + _NumOfChildrenSeats;
                    break;
                default:
                    break;
            }
            // get range of numbers
            int[] range = Enumerable.Range(0, numOfAvailableSeats+1).ToArray();
            // clear list before readding range
            selectedBox.Items.Clear();
            // add range to dropdown
            foreach (var item in range)
            {
                selectedBox.Items.Add(item);
            }
        }

        /// <summary>
        /// Set required number of seats when a drop down changes number
        /// </summary>
        private void SeatsTypeDropDowns_IndexChanged(object sender, EventArgs e)
        {
            ComboBox selectedBox = ((ComboBox)sender);
            // set selected count on selected drop down
            switch (selectedBox.Name)
            {
                case "comboBoxNormal":
                    _NumOfNormalSeats = (int)selectedBox.SelectedItem;
                    break;
                case "comboBoxChildren":
                    _NumOfChildrenSeats = (int)selectedBox.SelectedItem;
                    break;
                case "comboBoxSenior":
                    _NumOfSeniorSeats = (int)selectedBox.SelectedItem;
                    break;
                default:
                    break;
            }

            // if dropdown seats > 0 activate 'find seats' button, else deactivate
            buttonFindSeats.Enabled = (_NumOfNormalSeats + _NumOfChildrenSeats + _NumOfSeniorSeats) > 0;

            // if selected seats > 0 then activate 'book seats' button, otherwise disable
            buttonBookSeats.Enabled = _selectedSeats.Count > 0;
        }

        /// <summary>
        /// Implementations of callback interface method.
        /// Adds color to seats that has been clicked on by user.
        /// </summary>
        /// <param name="screenSeats">List of seats that has been clicked on by user or selected by best seats algorithm</param>
        public void OnAction(List<RemoteService.ScreenCast_Seat> screenSeats)
        {
            // for all the seats returned by call back
            foreach (RemoteService.ScreenCast_Seat ss in screenSeats)
            {
                // change seat status in screen seats to taken
                if (_screenSeats.Where(s => s.SeatId == ss.Seat.Id).Any())
                {
                    // create object which determines what happens when callback is called
                    SendOrPostCallback callback =
                        delegate(object state) 
                        {
                            _screenSeats.Find(sc => sc.Id == ss.Id).SeatAvailable = ss.SeatAvailable; // change seat status
                            ColorSeatsByStatus(); // recolor seats
                        };

                    // Only the thread that started the form, can edit the form. Therefore we post the callback to it.
                    _uiSyncContext.Post(callback, ss);
                }
            }
        }

        private void SeatGUI_FormClosing(object sender, FormClosingEventArgs e)
        {
            // unsubscribe from delegate/callback
            _remote.Unsubscribe(_screenCast);

            // free seats if window is closed irregularly
            if (this.DialogResult == DialogResult.Cancel || this.DialogResult == DialogResult.Abort || this.DialogResult == DialogResult.Ignore)
            {
                FreeSeats();
            }
        }
    }
}
